// Go作者版权所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

package flate

// dictDecoder实现了用于解压缩的LZ77滑动字典。
// LZ77通过两种形式的命令序列解压缩数据：
// None
// *文字插入：将一个或多个符号的运行插入到数据中
// 按原样流。这是通过
// 单个符号，或多个符号的writeSlice/writeMark组合。
// 如果没有预设字典，任何有效流都必须以文本插入开头
// 使用。
// None
// *反向复制：一个或多个符号的运行从以前的复制
// 发出的数据。反向拷贝是元组（dist，length），其中dist
// 确定要从流中复制的距离，长度确定复制的距离
// 要复制的字节数太多。请注意，长度大于的值是有效的
// 距离。由于LZ77使用转发副本，因此这种情况通常用于
// 对符号的重复运行执行一种形式的运行长度编码。
// writeCopy和tryWriteCopy用于实现此命令。
// None
// 出于性能方面的原因，此实现执行的功能很少甚至不健全
// 检查参数。因此，为每种类型记录的不变量
// 必须尊重方法调用。
type dictDecoder struct {
	hist []byte // 滑动窗口历史记录

	// 不变量：0<=rdPos<=wrPos<=len（历史）
	wrPos int  // 缓冲器中的电流输出位置
	rdPos int  // 已发出hist[：rdPos]
	full  bool // 是否已编写完整的窗口长度？
}

// init初始化dictDecoder，使其具有给定的滑动窗口字典
// 大小如果提供了预设dict，它将使用
// 口述的内容。
func (dd *dictDecoder) init(size int, dict []byte) {
	*dd = dictDecoder{hist: dd.hist}

	if cap(dd.hist) < size {
		dd.hist = make([]byte, size)
	}
	dd.hist = dd.hist[:size]

	if len(dict) > len(dd.hist) {
		dict = dict[len(dict)-len(dd.hist):]
	}
	dd.wrPos = copy(dd.hist, dict)
	if dd.wrPos == len(dd.hist) {
		dd.wrPos = 0
		dd.full = true
	}
	dd.rdPos = dd.wrPos
}

// histSize报告字典中历史数据的总量。
func (dd *dictDecoder) histSize() int {
	if dd.full {
		return len(dd.hist)
	}
	return dd.wrPos
}

// availRead报告readFlush可以刷新的字节数。
func (dd *dictDecoder) availRead() int {
	return dd.wrPos - dd.rdPos
}

// availWrite报告可用的输出缓冲区空间量。
func (dd *dictDecoder) availWrite() int {
	return len(dd.hist) - dd.wrPos
}

// writeSlice返回可将数据写入的可用缓冲区的一部分。
// None
// 将保留此不变量：len（s）<=availWrite（）
func (dd *dictDecoder) writeSlice() []byte {
	return dd.hist[dd.wrPos:]
}

// writeMark通过cnt使writer指针前进。
// None
// 必须保持此不变量：0<=cnt<=availWrite（）
func (dd *dictDecoder) writeMark(cnt int) {
	dd.wrPos += cnt
}

// writeByte将单个字节写入字典。
// None
// 必须保持此不变量：0<availWrite（）
func (dd *dictDecoder) writeByte(c byte) {
	dd.hist[dd.wrPos] = c
	dd.wrPos++
}

// writeCopy将给定（距离、长度）的字符串复制到输出。
// 这将返回复制的字节数，可能小于请求的字节数
// 长度（如果输出缓冲区中的可用空间太小）。
// None
// 必须保持此不变量：0<dist<=histSize（）
func (dd *dictDecoder) writeCopy(dist, length int) int {
	dstBase := dd.wrPos
	dstPos := dstBase
	srcPos := dstPos - dist
	endPos := dstPos + length
	if endPos > len(dd.hist) {
		endPos = len(dd.hist)
	}

	// 复制目标位置后的非重叠部分。
	// None
	// 此部分不重叠，因为此部分的副本长度
	// 始终小于或等于向后距离。这可能发生
	// 如果距离是指环绕在缓冲区中的数据。
	// 因此，这里执行向后复制；也就是说，中的确切字节数
	// 复制之前的源将放置在目标中。
	if srcPos < 0 {
		srcPos += len(dd.hist)
		dstPos += copy(dd.hist[dstPos:endPos], dd.hist[srcPos:])
		srcPos = 0
	}

	// 在目标位置之前复制可能重叠的部分。
	// None
	// 如果此部分的复制长度较大，则此部分可能重叠
	// 而不是向后的距离。这是LZ77允许的，因此重复
	// 可以使用（dist，length）对简洁地表示字符串。
	// 因此，这里执行转发拷贝；也就是说，复制的字节是
	// 可能取决于目标中作为副本的结果字节
	// 进展顺利。这在功能上等同于以下内容：
	// None
	// 对于i:=0；i<endPos-dstPos；i++{
	// dd.hist[dstPos+i]=dd.hist[srcPos+i]
	// }
	// dstPos=endPos
	// None
	for dstPos < endPos {
		dstPos += copy(dd.hist[dstPos:endPos], dd.hist[srcPos:dstPos])
	}

	dd.wrPos = dstPos
	return dstPos - dstBase
}

// tryWriteCopy尝试将给定（距离、长度）的字符串复制到
// 输出此专用版本针对短距离进行了优化。
// None
// 出于性能原因，此方法设计为内联。
// None
// 必须保持此不变量：0<dist<=histSize（）
func (dd *dictDecoder) tryWriteCopy(dist, length int) int {
	dstPos := dd.wrPos
	endPos := dstPos + length
	if dstPos < dist || endPos > len(dd.hist) {
		return 0
	}
	dstBase := dstPos
	srcPos := dstPos - dist

	// 在目标位置之前复制可能重叠的部分。
	for dstPos < endPos {
		dstPos += copy(dd.hist[dstPos:endPos], dd.hist[srcPos:dstPos])
	}

	dd.wrPos = dstPos
	return dstPos - dstBase
}

// readFlush返回准备就绪的历史缓冲区片段
// 发送给用户。必须完全使用readFlush返回的数据
// 在调用任何其他方法之前。
func (dd *dictDecoder) readFlush() []byte {
	toRead := dd.hist[dd.rdPos:dd.wrPos]
	dd.rdPos = dd.wrPos
	if dd.wrPos == len(dd.hist) {
		dd.wrPos, dd.rdPos = 0, 0
		dd.full = true
	}
	return toRead
}
